#!/usr/bin/env python
# -*- coding: utf-8 -*-
# Purpose: Node editor
# Created: 01/21/2009
# $Id $
# Copyright (c) 2008,2009 by University College London
# Authors:
# Didrik Pinte --<dpinte@dipole-consulting.com>
#
# This program is free software under the GPL (>=v2)
# Read the file COPYING coming with HydroPlatform for details.
'''
The object editor dialog allows users to edit the field attributes of an object
and its general properties
'''
import logging
import wx
import hydroplatform.ui.commands
from sqlalchemy.exc import DBAPIError
from sqlalchemy.orm.session import Session

from enthought.traits.api import HasTraits, Str, List, Button, \
     on_trait_change, Dict, Property, Instance, Any
from enthought.traits.ui.api import View, Item, Group, ListEditor, \
     EnumEditor, HGroup
from enthought.traits.ui.menu import OKCancelButtons, CloseAction

from hydroplatform.ui import fields as fd
from hydroplatform.model.network import Node, NodeType


class NonVisibleObjectsEditor(HasTraits):
    node_types = Dict()
    ntype_names = List(Str)
    ntype = Str(label="Node type")
    nodes = Dict()
    node_names = List(Str)
    node = Str
    edit_node = Button
    new_name = Str
    add_node = Button
    delete_node = Button
    
    context = Any
    
    traits_view = View(Item("ntype", editor=EnumEditor(name="ntype_names")),
                       Group(Item("node", editor=EnumEditor(name="node_names")),
                             HGroup(Item("edit_node", show_label=False), 
                                    Item("delete_node", show_label=False),
                                    springy=True
                                    ),
                             HGroup(Item("new_name"),
                                    Item("add_node", show_label=False, \
                                         enabled_when="len(new_name) > 0")), 
                             label="Edit a node"),
                       buttons=[CloseAction],
                       resizable=True,
                    )
    
    def __init__(self, **kwargs):
        super(HasTraits, self).__init__(**kwargs)
        self.update_nodes_list()
        
    def _ntype_default(self):
        if len(self.ntype_names) > 0:
            return self.ntype_names[0]
        else:
            return u"Invalid"
    
    def _ntype_names_default(self):
        return self.node_types.keys()
    
    @on_trait_change('ntype')
    def update_nodes_list(self):
        """
        When the ntype is changed, load the nodes
        """
        if self.ntype == u"Invalid":
            return
        # load the nodes for the given nodetype
        loaded_type = self.node_types[self.ntype]
        self.nodes.clear()
        for node in loaded_type.nodes:
            self.nodes[node.name] = node
        self.node_names = self.nodes.keys()
        if len(self.node_names) > 0:
            self.node = self.node_names[0]
        else : self.node = u"No node"
        
    def _edit_node_fired(self):
        frame = ObjectEditorDialog(self.context.mainwindow, 
                                   self.context, 
                                   self.nodes[self.node])
        frame.ShowModal()
    
    def _add_node_fired(self):
        new_node = Node(self.new_name, self.context.session.hpproject, \
                       self.node_types[self.ntype])  
        self.context.session.dbsession.add(new_node)
        self.context.session.dbsession.flush()
        self.nodes[self.new_name] = new_node
        self.node = self.new_name
        self.node_names.append(self.new_name)
        frame = ObjectEditorDialog(self.context.mainwindow, 
                                       self.context, 
                                       new_node)
        frame.ShowModal()  
    
    def _delete_node_fired(self):
        pass

class ObjectEditorDialog(wx.Dialog):
    '''
    This frame must show a notebook with a list of panels corresponding to the 
    datagroups in the edited node
    '''
    def __init__(self, parent,  context, object_instance):
        wx.Dialog.__init__(self, parent, -1, "Object editor",
            style = wx.DEFAULT_FRAME_STYLE,
            size=wx.Size(800, 600))
        self.SetSizeHints(800, 600)
        self.parent = parent
        self.name = "object_editor"

        self.obj = object_instance
        self.context = context

        # using a panel does not work on win32 with wx 2.8.10
        #panel = wx.Panel(self)
        self.notebook = wx.Notebook(self, -1)
        save_btn = wx.Button(self, -1, "Save")
       
        # add the general info panel
        self.add_general_info_panel()
        
        self.edited_node_values = list()
        
        # TEMPORARY FIX for missing groups
        for field in self.obj.type.fields:
            if field.group not in self.obj.type.groups:
                self.obj.type.groups.append(field.group)
        self.context.session.dbsession.flush()
        
        # for each group add a panel
        for group in self.obj.type.groups:
            self.add_panel_for_group(group)
            
        self.Bind(wx.EVT_BUTTON, self.on_save, save_btn)
        self.Bind(wx.EVT_CLOSE, self.on_close)
        
        # finally, put the notebook in a sizer for the panel to manage
        # the layout
        sizer = wx.BoxSizer(wx.VERTICAL)
        sizer.Add(self.notebook, 1, wx.EXPAND)
        sizer.Add(save_btn, 0, wx.CENTER)
        self.SetSizer(sizer)

        
    def add_general_info_panel(self):
        '''
        General tab in the notebook with the basic information about the object
        Nothing related to the fields
        '''
        general = wx.Panel(self.notebook)

        project = self.context.session.hpproject
        if isinstance(self.obj, Node):
            ntypes = [ntype.name for ntype in project.nodetypes]
        else:
            # this is a link
            ntypes = [ntype.name for ntype in project.linktypes]
            
        
        # text
        id_txt = wx.StaticText(general, -1, "Identifier")
        type_txt = wx.StaticText(general, -1, "Type")
        name_txt =  wx.StaticText(general, -1, "Name")
        notes_txt = wx.StaticText(general, -1, "Notes")
        #controls
        id_combo = wx.StaticText(general, -1, str(self.obj.id))
        type_combo = wx.StaticText(general, -1, self.obj.type.name)
        #type_combo = wx.ComboBox(general, -1, self.obj.type.name, 
        #                         (150, 30), wx.DefaultSize, ntypes,
        #                         style=wx.CB_READONLY)
        name_ctrl = wx.TextCtrl(general, -1, self.obj.name)
        notes = self.obj.notes        
        if notes is None:
            notes = ""
        notes_ctrl = wx.TextCtrl(general, -1, notes, size=(400, 100), 
                                 style=wx.TE_MULTILINE)
        sizer = wx.FlexGridSizer(cols=2, hgap=6, vgap=6)
        sizer.AddMany([id_txt, id_combo,
                       type_txt, type_combo, 
                       name_txt, name_ctrl, 
                       notes_txt, notes_ctrl])
        sizer.AddGrowableCol(1)        
        general.SetSizer(sizer)
        self.notebook.AddPage(general, "General")
        
        # EVENTS
        self.Bind(wx.EVT_TEXT, self.on_nameupdated, name_ctrl)
        self.Bind(wx.EVT_TEXT, self.on_notesupdated, notes_ctrl)
        
    def add_panel_for_group(self, fieldgroup):
        '''
        Not using wx.NotebookPanel because it seems broken/not working 
        with sizers !
        '''
        fields = [field for field in self.obj.type.fields \
                  if field.group is fieldgroup]
        
        if len(fields) == 0 :
            return
        
        # fill the Datagroup with its fields
        fgroup = fd.DataGroup(name=fieldgroup.name)
        for field in fields:
            if field.attribute_type.shared is False:
               # get value for the field for the current object
                nodevalue = self.obj.getFieldValue( \
                    self.context.session.dbsession, field) 
                edit_node = field.attribute_type.clone_traits()
                if nodevalue is None:
                    # if not existing, assign it a copy of the original field
                    nodevalue = self.obj.getNewValueForField(field)
                    try:
                        self.context.session.dbsession.add(nodevalue)
                        self.obj.values.append(nodevalue)
                    except DBAPIError, e:
                        logging.error(e)
                        self.context.session.dbsession.rollback()
                
                #The nodevalue.value contains the data part 
                # of the traits to edit
                edit_node.reference = nodevalue
                if nodevalue.value is not None:
                    try:
                        edit_node.data = nodevalue.value
                    except Exception, e:
                        logging.exception(e)
                        logging.error("Error while assigning value to field")
                    edit_node._updated = False
            else:
                edit_node = field.attribute_type
                
            
            if isinstance(edit_node, fd.Parameter):
                fgroup.parameters.append(edit_node)
            elif isinstance(edit_node, fd.TimeSerie):
                fgroup.timeseries.append(edit_node)
            elif isinstance(edit_node, fd.Table):
                fgroup.tables.append(edit_node)
            elif isinstance(edit_node, fd.SeasonalParameter):
                fgroup.sparameters.append(edit_node)
            elif isinstance(edit_node, fd.NodeReferences):
                fgroup.nodereferences.append(edit_node)                
            elif isinstance(edit_node, fd.LinkReferences):
                fgroup.linkreferences.append(edit_node)                 
                
            self.edited_node_values.append(edit_node)
        
        tree = fd.FieldValueEditorTree(name='general', group=fgroup)
        grouppanel = tree.edit_traits(kind='subpanel', 
                                       view=tree.get_editor_view(self.context),
                                       parent=self.notebook).control
        
        self.notebook.AddPage(grouppanel, fieldgroup.name.title()) 
        
    def on_nameupdated(self, event):
        '''
        Updates the object name from UI
        '''
        self.obj.name = event.GetString()
        
    def on_notesupdated(self, event):
        '''
        Updates object description based on UI
        '''
        self.obj.notes = event.GetString()
        
    
    def on_save(self, event):
        '''
        Save the current modification and resets the _updated trait attributes
        '''
        dbsession = self.context.session.dbsession
        if not self.check_if_can_save():
            return 
        try:
            dbsession.commit()
            # reset the _updated attribute of traits
            for e_node in self.edited_node_values:
                e_node._updated = False
            wx.MessageBox(u"Object saved successfully", "Saving object")
            dbsession.refresh(self.obj)
            self.context.mainwindow.canvas.full_redraw() 
        except DBAPIError, exception:            
            dbsession.rollback()
            raise(exception)
    
    def check_if_can_save(self):
        """
        Checks the object content to verify if we can save it to the db
        """
        can_save = True
        for node_value in self.edited_node_values:
                if node_value.data is None:
                    wx.MessageBox(
u'''The data part of the field %s is empty. This is invalid''' % node_value.name, 
                                  u"Error while saving object")
                    can_save = False
        return can_save
 
    
    def on_close(self, event):
        '''
        Before closing, check if there is something to save or not
        '''
        # if changes -> save them
        dbsession = self.context.session.dbsession
        
        if not self.check_if_can_save():
            dlg = wx.MessageDialog(self, \
u'''There was an error checking your object fields. 
Do you really want to close the editor and rollback your modifications ?''', \
                    u"Error while checking object fields",
                    style=wx.OK| wx.CANCEL | wx.CENTER)
            if dlg.ShowModal():
                dbsession.rollback()
                self.Destroy()
                return
            else:
                return
        
        must_save = False
        if dbsession.is_modified(self.obj): 
            must_save = True
        else:
            for node_value in self.edited_node_values:
                if node_value._updated is True:
                    must_save = True
                    break
        if must_save:
            #ask the user to confirm saving the changes
            dlg = wx.MessageDialog(self,  \
                u"Do you want to save the changes to this object ?", \
                u"It has been modified", \
                wx.YES_NO | wx.ICON_QUESTION)
            return_code = dlg.ShowModal()
            if return_code == wx.ID_YES:
                dbsession.commit()
            else: 
                dbsession.rollback()                
        else:
            dbsession.refresh(self.obj)
            # doing a rollback causes exceptions because of the array 
            # comparison in sqlalchemy : 
            # ValueError: The truth value of an array with more than one element is 
            #ambiguous. Use a.any() or a.all()
            #dbsession.rollback()
        self.Destroy()    
